package com.comba.nutz.boot.starter.dao;

import org.nutz.dao.*;
import org.nutz.dao.entity.Record;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.impl.NutDao;
import org.nutz.dao.impl.sql.callback.FetchIntegerCallback;
import org.nutz.dao.impl.sql.callback.FetchMapCallback;
import org.nutz.dao.impl.sql.callback.QueryMapCallback;
import org.nutz.dao.pager.Pager;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.lang.Each;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.plugins.sqltpl.impl.beetl.BeetlSqlTpl;
import org.nutz.resource.Scans;
import org.nutz.trans.Molecule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

/**
 * @author zhangjp
 * @date 2016/8/11.
 */
@Component
public class SpringNutDao extends NutDao {

    private static final Log log = Logs.get();

    public SpringNutDao(@Autowired DataSource dataSource, @Autowired SqlManager sqlManager) {
        super(dataSource, sqlManager);

        //全局启用，支持field 对应为数据表的蛇底式小写(snake_case)的字段
        Daos.FORCE_HUMP_COLUMN_NAME = true;

        //全局启用，SqlTpl
        Sqls.setSqlBorning(BeetlSqlTpl.class);
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 获取指定key的sql语句
     */
    public String getSql(String key) {
        return sqls().get(key);
    }

    public <T> List<T> queryWithCount(Class<T> classOfT, Condition condition, Pager pager) {
        List<T> list = super.query(classOfT, condition, pager);

        if (pager != null) {
            pager.setRecordCount(count(classOfT, condition));
        }

        return list;
    }

    public <T> List<T> query(Class<T> classOfT, Condition condition, Pager pager) {
        List<T> list = super.query(classOfT, condition, pager);

        return list;
    }

    public <T> List<T> query(Class<T> classOfT, Map<String, Object> filters, Pager pager) {
        CndPlus cndPlus = CndPlus.fromMap(filters);

        return query(classOfT, cndPlus, pager);
    }

    public <T> List<T> queryWithCount(Class<T> classOfT, Map<String, Object> filters, Pager pager) {
        CndPlus cndPlus = CndPlus.fromMap(filters);

        return queryWithCount(classOfT, cndPlus, pager);
    }

    public List<Record> query(String tableNameOrSql) {
        if (tableNameOrSql.toLowerCase().contains("select")) {
            return super.query("(" + tableNameOrSql + ") autoTable", null, null);
        } else {
            return super.query(tableNameOrSql, null, null);
        }
    }

    public List<Record> query(String tableNameOrSql, Condition condition, Pager pager) {
        if (tableNameOrSql.toLowerCase().contains("select")) {
            return super.query("(" + tableNameOrSql + ") autoTable", condition, pager);
        } else {
            return super.query(tableNameOrSql, condition, pager);
        }
    }

    public List<Record> query(String tableNameOrSql, Map<String, Object> filters, Pager pager) {
        Condition condition = CndPlus.fromMap(filters);
        if (tableNameOrSql.toLowerCase().contains("select")) {
            return super.query("(" + tableNameOrSql + ") autoTable", condition, pager);
        } else {
            return super.query(tableNameOrSql, condition, pager);
        }
    }

    public <T> List<T> query(Class<T> classOfT, Condition cnd) {
        return query(classOfT, cnd, null);
    }

    public <T> List<T> query(Class<T> classOfT, Map<String, Object> filters) {
        return query(classOfT, filters, null);
    }

    public <T> T insert(final T obj, String active) {
        Object first = Lang.first(obj);

        if (null == first)
            return obj;

        if (Strings.isBlank(active))
            return insert(obj);

        Molecule m = new Molecule() {
            public void run() {
                setObj(insert(obj));
            }
        };

        FieldFilter.create(first.getClass(), active).run(m);
        return (T) m.getObj();
    }

    public List<Map<String, Object>> queryBySqlKey(String sqlKey, Map<String, Object> params, Pager pager) {
        Sql sql = sqls().create(sqlKey);

        return queryBySql(sql, params, pager, false);
    }

    public List<Map<String, Object>> queryBySqlKeyWithCount(String sqlKey, Map<String, Object> params, Pager pager) {
        Sql sql = sqls().create(sqlKey);

        return queryBySql(sql, params, pager, true);
    }

    public List<Map<String, Object>> queryBySql(String sql, Map<String, Object> params, Pager pager) {
        Sql sql1 = Sqls.create(sql);

        return queryBySql(sql1, params, pager, false);
    }

    public List<Map<String, Object>> queryBySqlWithCount(String sql, Map<String, Object> params, Pager pager) {
        Sql sql1 = Sqls.create(sql);

        return queryBySql(sql1, params, pager, true);
    }

    public List<Map<String, Object>> queryBySql(Sql sql,  Map<String, Object> params, Pager pager, boolean count) {
        if (pager != null) {
            sql.setPager(pager);
            if(count) {
                Sql countSql = Sqls.create(getCountSql(sql.getSourceSql()));
                for (Map.Entry param : params.entrySet()) {
                    if (param.getKey().toString().contains("$")) {
                        countSql.setVar(param.getKey().toString().substring(1), param.getValue());
                    } else {
                        countSql.setParam(param.getKey().toString(), param.getValue());
                    }
                }
                countSql.setCallback(new FetchIntegerCallback());
                execute(countSql);
                pager.setRecordCount((Integer) countSql.getResult());
            }
        }

        for(Map.Entry param : params.entrySet()) {
            if(param.getKey().toString().contains("$")) {
                sql.setVar(param.getKey().toString().substring(1), param.getValue());
            } else {
                sql.setParam(param.getKey().toString(), param.getValue());
            }
        }
        sql.setCallback(new QueryMapCallback());
        execute(sql);

        return (List<Map<String, Object>>) sql.getResult();
    }

    public Map<String, Object> fetchBySqlKey(String sqlKey, Map<String, Object> params) {
        Sql sql = sqls().create(sqlKey);

        return fetchBySql(sql, params);
    }

    public Map<String, Object> fetchBySql(String sql, Map<String, Object> params) {
        Sql sql1 = Sqls.create(sql);

        return fetchBySql(sql1, params);
    }

    public Map<String, Object> fetchBySql(Sql sql,  Map<String, Object> params) {
        for(Map.Entry param : params.entrySet()) {
            if(param.getKey().toString().contains("$")) {
                sql.setVar(param.getKey().toString().substring(1), param.getValue());
            } else {
                sql.setParam(param.getKey().toString(), param.getValue());
            }
        }
        sql.setCallback(new FetchMapCallback());
        execute(sql);

        return (Map<String, Object>) sql.getResult();
    }

    /**
     * 批量删除，支持集合
     */
    public int delete(Object obj) {
        if (null == obj)
            return -1;

        final int[] re = new int[1];
        if (Lang.length(obj) > 0) {
            Lang.each(obj, new Each<Object>() {
                public void invoke(int i, Object ele, int length) {
                    re[0] = re[0] + singleDelete(ele);
                }
            });
        }

        return re[0];
    }

    /**
     * 单个对象删除
     */
    public int singleDelete(Object obj) {
        return super.delete(obj);
    }

    /**
     * 取前n条记录
     */
    public Pager limit(int n) {
        return createPager(1, n);
    }

    public int count(String tableNameOrSql) {
        return count(tableNameOrSql, null);
    }

    public int count(String tableNameOrSql, Condition condition) {
        if (tableNameOrSql.toLowerCase().contains("select")) {
            return super.count("(" + tableNameOrSql + ") autoTable", condition);
        } else {
            return super.count(tableNameOrSql, condition);
        }
    }

    public String getCountSql(String sql) {
         return "select count(1) from (" + sql + ") autoTable";
    }

    /**
     * 自动扫描指定包路径，并创建实体表
     */
    public void buildTable(boolean dropIfExists, String... packages) {
        for (String it : packages) {
            for (Class<?> clazz : Scans.me().scanPackage(it)) {
                if (clazz.getAnnotation(Table.class) != null) {
                    create(clazz, dropIfExists);
                }
            }
        }
    }

    //重写后支持spring事务管理
    @Override
    public void run(ConnCallback callback) {
        Connection con = DataSourceUtils.getConnection(dataSource);
        try {
            callback.invoke(con);
        } catch (Exception e) {
            if (e instanceof RuntimeException)
                throw (RuntimeException) e;
            else
                throw new RuntimeException(e);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }
    }
}